組込み現場の「C++」プログラミング 明日から使える徹底入門

高木 信尚(株式会社クローバーフィールド

3.8 例外指定の振る舞い

C++では,関数に「例外指定」を付けることができます.例外指定は,その関数がどんな例外を送出する可能性があるかを指定するための文法です.例外指定がない場合,その関数はあらゆる例外を送出する可能性があります.例外指定の記述のしかたは次のようになります.

void f() throw();
void g() throw(E);
void h() throw(E1, E2);
T operator+(const T&, const T&) throw(E);

上記のコードにおいて,f関数は例外が送出されないことを指定しています.g関数はE型の例外が送出される可能性があることを指定しています.そして,h関数はE1型またはE2型の例外が送出される可能性があることを指定しています.最後の行のように,多重定義された演算子にも例外指定を付けることができます.必要に応じて,例外指定では送出される可能性がある型をいくつでも並べることが可能です.

例外指定は,その関数を呼び出す側に対する表明でしかありません.したがって,本当に指定したとおりの例外しか送出しないことは,関数の実装側で保証する必要があります.もし,例外指定に並べた型以外の例外が送出された場合,関数のスタック巻き戻しのあと(すなわち,自動オブジェクトのデストラクタが呼ばれた後),unexpected関数が呼び出されます.

unexpected関数は,決して呼び出し元に戻ることはありませんが,例外を送出することならできます.unexpected関数の処理内容は,デフォルトではterminate関数を呼び出すだけですが,set_unexpected関数を使用すれば,ユーザー定義の処理に置き換えることができます.なお,terminate関数は,デフォルトではabort関数を呼び出してプログラムを異常終了させます.

unexpected関数から何らかの例外が送出された場合,それが元の関数の例外指定で許可された型であれば,そのままその例外が送出されることになります.そうでない場合,bad_exceptionクラスのオブジェクトが例外として送出されます.元の関数の例外指定で,bad_exceptionが許可されていれば,そのまま例外として送出されますが,許可されていない場合は,terminate関数が呼び出されます.

文章での説明だけではわかりにくいので,具体例を挙げてみます(例によって,std::は省略しています).

class E1 {};
class E2 {};
void handler()
{
    throw E2();
}
void f() throw(bad_exception)
{
    throw E1();
}
void g() throw()
{
    throw E1();
}
int main()
{
    set_unexpected(&handler);
    try
    {
        f();
    }
    catch (bad_exception& e)
    {
    }    try
    {
        g();
    }
    catch (bad_exception& e)
    {
    }
}

このコードでは,unexpected関数が呼ばれたときの処理をhandler関数に置き換えるために,最初にset_unexpected関数を呼び出しています.そして,f関数とg関数を順に呼び出しています.

f関数もg関数も,例外指定で許可していないE1クラスの例外を送出しているので,unexpected関数経由でhandler関数が呼び出されます.ここで,再びE2クラスの例外が送出されます.しかし,f関数もg関数もE2クラスもまた,許可していませんので,bad_exceptionクラスの例外に置き換えられてしまいます.f関数の例外指定ではbad_exceptionを許可していますから,main関数から見れば,f関数がbad_exceptionクラスの例外を送出したものとして振る舞います.しかし,g関数の例外指定ではbad_exceptionを許可していないので,terminate関数が呼び出されて,プログラムは異常終了してしまいます.

またしても,ここまでの説明はあくまでも規格上の振る舞いです.実在する処理系では例外指定が正しく振る舞わない場合が多々あります.また,例外指定を使用すると,実際に送出された例外が許可されたものかどうかを判別する処理などが追加され,かなりのオーバーヘッドになります.さらに,インライン関数に例外指定があると,無条件にインライン置換が抑制されてしまう処理系もあります.ほかにもいくつかの理由があり,現在では,例外を送出しないことの表明としてのthrow()を除いて,例外指定は使用しないのが主流になっています.